/*
 * Copyright 2011-2012 Rivoli team
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.googlecode.rivoli.model.classhierarchy

import com.googlecode.rivoli.RivoliException
import com.googlecode.rivoli.model.AttributeDescriptor
import com.googlecode.rivoli.model.Element
import com.googlecode.rivoli.model.ElementDescriptor

import java.lang.reflect.Field

class ElementConverter {
	static <T> T convert(Element element, Class<T> elementClass) {
		T object = createObject(element.descriptor, elementClass)
		setAttributes(object, element, element.descriptor)
		convertChildren(object, element)

		storeElementReference(object, element)

		object
	}

	private static <T> T createObject(ElementDescriptor elementDescriptor, Class<T> elementClass) {
		T object = elementClass.newInstance()

		String elementClassName = elementClass.simpleName.humanize()

		Set<String> possibleElementClassNames = getPossibleElementClassNames(elementDescriptor)

		if (!(elementClassName in possibleElementClassNames)) {
			throw new RivoliException("unexpected element name '${elementClassName}', expected one of ${possibleElementClassNames}")
		}

		object
	}

	private static Set<String> getPossibleElementClassNames(ElementDescriptor elementDescriptor) {
		Set<String> possibleElementClassNames = []

		ElementDescriptor currentElementDescriptor = elementDescriptor

		while (currentElementDescriptor) {
			possibleElementClassNames << currentElementDescriptor.name

			currentElementDescriptor = currentElementDescriptor.parent
		}

		possibleElementClassNames
	}

	private static void setAttributes(Object object, Element element, ElementDescriptor elementDescriptor) {
		ElementDescriptor parentDescriptor = elementDescriptor.parent

		if (parentDescriptor) {
			setAttributes(object, element, parentDescriptor)
		}

		elementDescriptor.attributeDescriptors.each {AttributeDescriptor attributeDescriptor ->
			String attributeName = attributeDescriptor.name

			MetaProperty property = object.hasProperty(attributeName)

			if (!property) {
				throw new RivoliException("element class '${object.class.simpleName}' does not have a property named '${attributeName}'")
			} else if (property.type != attributeDescriptor.type) {
				throw new RivoliException("property '${attributeName}' of element class '${object.class.simpleName}' is not of type '${attributeDescriptor.type.simpleName}'")
			} else {
				object[attributeName] = element.getAttribute(attributeName)
			}
		}
	}

	private static void convertChildren(Object object, Element element) {
		if (element.descriptor.containerFor) {
			List<Object> children = []

			element.children.each {Element child ->
				children << convert(child, getClassBasedDescriptor(child).elementDescriptorClass)
			}

			Field containerField = Utilities.findContainerField(object.class, true)
			containerField.accessible = true
			containerField.set(object, children)
		}
	}

	private static ClassBasedElementDescriptor getClassBasedDescriptor(Element element) {
		ElementDescriptor descriptor = element.descriptor

		if (!(descriptor in ClassBasedElementDescriptor)) {
			throw new RivoliException("element descriptor '${descriptor.name}' is not class based")
		}

		descriptor
	}

	private static void storeElementReference(Object object, Element element) {
		if (object.hasProperty('element')) {
			throw new RivoliException("element '${element.descriptor.name}' can't be converted into an object of type ${object.class.simpleName}, since it has an attribute named 'element'")
		}

		object.metaClass.element = element
	}
}